/*
 * This example demonstrates the libgit2 rev walker APIs to roughly
 * simulate the output of `git log -1`.
 */

#include "common.h"

#include <assert.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/time.h>

/* log_state represents walker being configured while handling options */
struct log_state {
	git_repository *repo;
	const char *repodir;
	git_revwalk *walker;
        const char *ref;
	int hide;
	int sorting;
};
static struct log_state s;

static void *xmalloc(size_t size)
{
        void *p = malloc(size);
        if (!p) {
                fprintf(stderr, "OOM!\n");
                exit(-1);
        }
        memset(p, 0, size);
        return p;
}

/* Helper to find how many files in a commit changed from its nth parent. */
static int match_with_parent(git_commit *commit, int i, git_diff_options *opts)
{
	git_commit *parent;
	git_tree *a, *b;
	git_diff *diff;
	int ndeltas;

	check_lg2(
		git_commit_parent(&parent, commit, (size_t)i), "Get parent", NULL);
	check_lg2(git_commit_tree(&a, parent), "Tree for parent", NULL);
	check_lg2(git_commit_tree(&b, commit), "Tree for commit", NULL);
	check_lg2(
		git_diff_tree_to_tree(&diff, git_commit_owner(commit), a, b, opts),
		"Checking diff between parent and commit", NULL);

	ndeltas = (int)git_diff_num_deltas(diff);

        git_diff_free(diff);
        git_tree_free(a);
        git_tree_free(b);
        git_commit_free(parent);

        return ndeltas > 0;
}

/* Helper to get the latest commit of the specified file */
int git_show_last_commit(char *filename, char *buf)
{
        git_oid oid;
        git_commit *commit = NULL;

        /* Set up pathspec. */
        git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
        diffopts.pathspec.strings = &filename;
        diffopts.pathspec.count = 1;

        /* Use the revwalker to traverse the history. */
        check_lg2(git_revwalk_push_ref(s.walker, s.ref),
                        "Could not find repository reference", NULL);

        for (; !git_revwalk_next(&oid, s.walker); git_commit_free(commit)) {
                check_lg2(git_commit_lookup(&commit, s.repo, &oid),
                                "Failed to look up commit", NULL);

                int parents = (int)git_commit_parentcount(commit);
                int unmatched = parents;
                if (parents == 0) {
                        git_tree *tree;
                        git_pathspec *ps;
                        check_lg2(git_commit_tree(&tree, commit), "Get tree", NULL);
                        check_lg2(git_pathspec_new(&ps, &diffopts.pathspec),
                                        "Building pathspec", NULL);
                        if (git_pathspec_match_tree(
                                                NULL, tree, GIT_PATHSPEC_NO_MATCH_ERROR, ps) != 0)
                                unmatched = 1;
                        git_pathspec_free(ps);
                        git_tree_free(tree);
                } else {
                        int i;
                        for (i = 0; i < parents; ++i) {
                                if (match_with_parent(commit, i, &diffopts))
                                        unmatched--;
                        }
                }
                if (unmatched > 0)
                        continue;

                git_oid_tostr(buf, GIT_OID_HEXSZ + 1, git_commit_id(commit));
                git_commit_free(commit);
                break;
        }
        git_revwalk_reset(s.walker);
        return 0;
}

int main(int argc, char *argv[])
{
#define MAX_FILE_PATH  1024
        DIR *dir;
        int i, count;
        struct stat st;
        struct dirent *direp;
        struct timeval start, end;
        char filename[MAX_FILE_PATH], repodir[64];
        char *basedir, *subdir, *ref = NULL;
        char *filenames[8192];
        char buf[GIT_OID_HEXSZ + 1];

        if (argc != 3) {
                fprintf(stderr, "Usage: ./log basedir subdir\n");
                exit(-1);
        }

        basedir = argv[1];
        subdir = argv[2];
        strcpy(repodir, basedir);
        strcat(repodir, "/.git");
        strcpy(filename, basedir);
        strcat(filename, "/");
        strcat(filename, subdir);
        if (stat(filename, &st) < 0) {
                fprintf(stderr, "Not valid file path: \"%s\"!\n", filename);
                exit(-2);
        }

        memset(&s, 0, sizeof(s));
        s.sorting = GIT_SORT_TIME;
        s.hide = 0;
        s.repodir = strlen(repodir) > 0  ? repodir : "/tmp/libgit2/.git";
        s.ref = ref ? ref : "refs/heads/master";

        /* Init libgit2 library */
        git_libgit2_init();

        /* Open repo. */
        check_lg2(git_repository_open_ext(&s.repo, s.repodir, 0, NULL),
                "Could not open repository", s.repodir);

	/* Create revwalker. */
        check_lg2(git_revwalk_new(&s.walker, s.repo),
                "Could not create revision walker", NULL);
        git_revwalk_sorting(s.walker, s.sorting);

        if (S_ISREG(st.st_mode)) {
                /* Regular file */
                filenames[0] = xmalloc(MAX_FILE_PATH);
                strcpy(filenames[0], subdir);
                count = 1;
        } else if (S_ISDIR(st.st_mode)) {
                /* Directory */
                dir = opendir(filename);
                count = 0;
                while ((direp = readdir(dir)) != NULL) {
                        if (strcmp(direp->d_name, ".") && strcmp(direp->d_name, "..") &&
                                strcmp(direp->d_name, ".git")) {
                                /* no '.' '..' or '.git' */
                                char *name = filenames[count] = xmalloc(MAX_FILE_PATH);
                                if (*subdir != '.') {
                                        strcpy(name, subdir);
                                        if (name[strlen(name) - 1] != '/') {
                                                strcat(name, "/");
                                        }
                                } else {
                                        name[0] = '\0';
                                }
                                strcat(name, direp->d_name);
                                count++;
                        }
                }
        } else {
                assert(0);
        }

        for (i = 0; i < count; i++) {
                fprintf(stdout, "filename: %s\n", filenames[i]);
                memset(buf, 0, sizeof(buf));
                gettimeofday(&start, NULL);
                git_show_last_commit(filenames[i], buf);
                gettimeofday(&end, NULL);
                free(filenames[i]);
                if (strlen(buf) > 0) {
                        fprintf(stdout, "commit: %s\n", buf);
                } else {
                        fprintf(stdout, "no commit record!\n");
                }
                fprintf(stdout, "time span: %ld(ms)\n",
                                (end.tv_sec - start.tv_sec) * 1000 + \
                                (end.tv_usec - start.tv_usec) / 1000);
        }

	git_revwalk_free(s.walker);
	git_repository_free(s.repo);
	git_libgit2_shutdown();

        return 0;
}
